using Sustainalytics.Entities.ESGRatings;
using Sustainalytics.ESGRatings.BusinessLogic.Computing.Utils;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sustainalytics.ESGRatings.BusinessLogic.Computing
{
    public class ComparativeGroupIndexes<TU, TV, TK>
    {
        private readonly Dictionary<TU, int> _directIndex = new Dictionary<TU, int>();

        private readonly List<TU> _reverseIndex = new List<TU>();

        public int Count
        {
            get
            {
                return _reverseIndex.Count;
            }
        }

        private int[] FirstLevelGroupByHeads { get; set; }

        private int[] SecondLevelGroupByHeads { get; set; }

        public static ComparativeGroupIndexes<TU, TV, TK> Create<T>(
            IEnumerable<T> items,
            Func<T, TU> keySelector,
            Func<T, TV> sortKeySelector,
            IComparer<TV> comparer,
            Func<T, TK> secondSortKeySelector,
            IComparer<TK> secondComparer)
        {
            var returnIndex = new ComparativeGroupIndexes<TU, TV, TK>();
            if (items == null)
            {
                return returnIndex;
            }

            int currentIndex = 0;

            var heads = new List<int>();
            var secondHeads = new List<int>();
            T previousItem = default(T);
            bool first = true;
            foreach (var item in items.OrderBy(sortKeySelector, comparer).ThenBy(secondSortKeySelector, secondComparer))
            {
                if (!first)
                {
                    if (comparer.Compare(sortKeySelector(previousItem), sortKeySelector(item)) != 0)
                    {
                        heads.Add(currentIndex);
                    }

                    if (secondComparer.Compare(secondSortKeySelector(previousItem), secondSortKeySelector(item)) != 0)
                    {
                        secondHeads.Add(currentIndex);
                    }
                }

                first = false;
                var projectedItem = keySelector(item);
                returnIndex._directIndex.Add(projectedItem, currentIndex);
                returnIndex._reverseIndex.Add(projectedItem);

                ++currentIndex;
                previousItem = item;
            }

            returnIndex.FirstLevelGroupByHeads = heads.ToArray();
            returnIndex.SecondLevelGroupByHeads = secondHeads.ToArray();

            return returnIndex;
        }

        public IEnumerable<TU> GetComparativeGroupCompanies(TU companyId, ComparativeGroupEnum groupBy)
        {
            int[] heads = GroupByHeads(groupBy);
            return GetComparativeGroupCompanies(companyId, heads);
        }

        public int GetIndex(TU key)
        {
            int index;
            if (_directIndex.TryGetValue(key, out index))
            {
                return index;
            }
            return -1;
        }

        public TU GetItem(int index)
        {
            return _reverseIndex[index];
        }

        public TU[] GetReverseIndexCopy()
        {
            return _reverseIndex.ToArray();
        }

        public int[] GroupByHeads(ComparativeGroupEnum groupBy)
        {
            return groupBy == ComparativeGroupEnum.IndustryGroup ? FirstLevelGroupByHeads : SecondLevelGroupByHeads;
        }

        private IEnumerable<TU> GetComparativeGroupCompanies(TU companyId, int[] heads)
        {
            var companyIndex = GetIndex(companyId);
            var upper = 0;
            var lower = 0;
            var boundedHeads = heads.Union(_directIndex.Count.Yield());

            foreach (var item in boundedHeads)
            {
                lower = upper;
                upper = item;
                if (lower <= companyIndex && companyIndex < upper)
                {
                    break;
                }
            }
            return Enumerable.Range(lower, upper - lower).Select(GetItem);
        }
    }
}